import marked from '../parser/marked'
import Prism from 'prismjs'
import katex from 'katex'
import 'katex/dist/contrib/mhchem.min.js'
import loadRenderer from '../renderers'
import githubMarkdownCss from 'github-markdown-css/github-markdown.css'
import exportStyle from '../assets/styles/exportStyle.css'
import highlightCss from 'prismjs/themes/prism.css'
import katexCss from 'katex/dist/katex.css'
import footerHeaderCss from '../assets/styles/headerFooterStyle.css'
import { EXPORT_DOMPURIFY_CONFIG } from '../config'
import { sanitize, unescapeHTML } from '../utils'
import { validEmoji } from '../ui/emojis'

export const getSanitizeHtml = (markdown, options) => {
  const html = marked(markdown, options)
  return sanitize(html, EXPORT_DOMPURIFY_CONFIG, false)
}

const DIAGRAM_TYPE = [
  'mermaid',
  'flowchart',
  'sequence',
  'plantuml',
  'vega-lite'
]

class ExportHtml {
  constructor (markdown, muya) {
    this.markdown = markdown
    this.muya = muya
    this.exportContainer = null
    this.mathRendererCalled = false
  }

  async renderMermaid () {
    const codes = this.exportContainer.querySelectorAll('code.language-mermaid')
    if (codes.length === 0) {
      return // 如果没有mermaid代码块，直接返回
    }

    for (const code of codes) {
      const preEle = code.parentNode
      const mermaidContainer = document.createElement('div')
      mermaidContainer.innerHTML = sanitize(unescapeHTML(code.innerHTML), EXPORT_DOMPURIFY_CONFIG, true)
      mermaidContainer.classList.add('mermaid')
      preEle.replaceWith(mermaidContainer)
    }

    try {
      const mermaid = await loadRenderer('mermaid')

      // 设置Mermaid主题
      let theme = 'default'
      if (this.muya) {
        theme = this.muya.options.mermaidTheme
      }

      // We only export light theme, so set mermaid theme to `default`, in the future, we can choose whick theme to export.
      mermaid.initialize({
        securityLevel: 'strict',
        theme: theme
      })

      // Wait for mermaid to render all diagrams
      const mermaidElements = this.exportContainer.querySelectorAll('div.mermaid')
      if (mermaidElements.length > 0) {
        await mermaid.init(undefined, mermaidElements)

        // 将SVG转换为PNG图片，以便Pandoc正确处理
        await this.convertMermaidSvgsToImages(mermaidElements)
      }
    } catch (error) {
      console.error('Mermaid rendering error:', error)
      // 如果渲染失败，至少确保元素存在
      const mermaidElements = this.exportContainer.querySelectorAll('div.mermaid')
      for (const element of mermaidElements) {
        if (!element.innerHTML || element.innerHTML.trim() === '') {
          element.innerHTML = '<!-- Mermaid diagram failed to render -->'
        }
      }
    }
  }

  async convertMermaidSvgsToImages (mermaidElements) {
    // 完全移除PNG转换逻辑，只保留SVG格式
    // 这样可以确保在Word导出中保持矢量图形的质量
    for (const element of mermaidElements) {
      const svgElement = element.querySelector('svg')
      if (svgElement) {
        try {
          // 为SVG添加样式确保在Word中正确显示
          svgElement.style.maxWidth = '100%'
          svgElement.style.height = 'auto'
          svgElement.style.display = 'block'
          svgElement.style.margin = '0 auto'

          // 确保SVG有正确的viewBox属性
          if (!svgElement.getAttribute('viewBox')) {
            try {
              const bbox = svgElement.getBBox()
              if (bbox.width && bbox.height) {
                svgElement.setAttribute('viewBox', `0 0 ${bbox.width} ${bbox.height}`)
              }
            } catch (error) {
              // 如果获取BBox失败，使用默认值
              svgElement.setAttribute('viewBox', '0 0 800 600')
            }
          }

          // 确保SVG有尺寸属性
          if (!svgElement.getAttribute('width')) {
            svgElement.setAttribute('width', '100%')
          }
          if (!svgElement.getAttribute('height')) {
            svgElement.setAttribute('height', 'auto')
          }

          // 添加preserveAspectRatio属性确保在不同设备上正确显示
          svgElement.setAttribute('preserveAspectRatio', 'xMidYMid meet')

          // 确保所有路径都有明确的颜色属性（解决Word中颜色问题）
          const paths = svgElement.querySelectorAll('path')
          paths.forEach(path => {
            if (!path.getAttribute('fill') || path.getAttribute('fill') === 'none') {
              path.setAttribute('fill', 'none')
            }
            if (!path.getAttribute('stroke')) {
              path.setAttribute('stroke', 'currentColor')
            }
          })
        } catch (error) {
          console.error('Failed to process SVG:', error)
          // 保留原始SVG，但添加一些样式确保可见性
          svgElement.style.maxWidth = '100%'
          svgElement.style.height = 'auto'
          svgElement.style.display = 'block'
        }
      }
    }
  }

  async fallbackSvgToImage (svgElement, containerElement) {
    // 备用方案：直接保留SVG格式
    // 为SVG添加样式确保在Word中正确显示
    svgElement.style.maxWidth = '100%'
    svgElement.style.height = 'auto'
    svgElement.style.display = 'block'
    svgElement.style.margin = '0 auto'

    // 确保SVG有正确的viewBox属性
    if (!svgElement.getAttribute('viewBox')) {
      try {
        const bbox = svgElement.getBBox()
        if (bbox.width && bbox.height) {
          svgElement.setAttribute('viewBox', `0 0 ${bbox.width} ${bbox.height}`)
        }
      } catch (error) {
        // 如果获取BBox失败，使用默认值
        svgElement.setAttribute('viewBox', '0 0 800 600')
      }
    }

    // 确保SVG有尺寸属性
    if (!svgElement.getAttribute('width')) {
      svgElement.setAttribute('width', '100%')
    }
    if (!svgElement.getAttribute('height')) {
      svgElement.setAttribute('height', 'auto')
    }

    // 添加preserveAspectRatio属性确保在不同设备上正确显示
    svgElement.setAttribute('preserveAspectRatio', 'xMidYMid meet')
  }

  async svgToPng (svgElement) {
    // 保留SVG格式而不是转换为PNG
    // 返回SVG数据URL
    try {
      // 克隆SVG元素以避免修改原始元素
      const clonedSvg = svgElement.cloneNode(true)

      // 确保SVG有正确的viewBox属性
      if (!clonedSvg.getAttribute('viewBox')) {
        try {
          const bbox = svgElement.getBBox()
          if (bbox.width && bbox.height) {
            clonedSvg.setAttribute('viewBox', `0 0 ${bbox.width} ${bbox.height}`)
          }
        } catch (error) {
          // 如果获取BBox失败，使用默认值
          clonedSvg.setAttribute('viewBox', '0 0 800 600')
        }
      }

      // 确保SVG有尺寸属性
      if (!clonedSvg.getAttribute('width')) {
        clonedSvg.setAttribute('width', '100%')
      }
      if (!clonedSvg.getAttribute('height')) {
        clonedSvg.setAttribute('height', 'auto')
      }

      // 添加preserveAspectRatio属性确保在不同设备上正确显示
      clonedSvg.setAttribute('preserveAspectRatio', 'xMidYMid meet')

      // 添加样式
      clonedSvg.style.maxWidth = '100%'
      clonedSvg.style.height = 'auto'
      clonedSvg.style.display = 'block'
      clonedSvg.style.margin = '0 auto'

      // 将SVG转换为字符串
      const svgString = new XMLSerializer().serializeToString(clonedSvg)

      // 添加XML声明和命名空间
      const svgWithXml = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>${svgString}`

      // 返回SVG数据URL
      return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgWithXml)
    } catch (error) {
      throw new Error('Failed to convert SVG to data URL: ' + error.message)
    }
  }

  async renderDiagram () {
    const selector = 'code.language-vega-lite, code.language-flowchart, code.language-sequence, code.language-plantuml'
    const RENDER_MAP = {
      flowchart: await loadRenderer('flowchart'),
      sequence: await loadRenderer('sequence'),
      plantuml: await loadRenderer('plantuml'),
      'vega-lite': await loadRenderer('vega-lite')
    }
    const codes = this.exportContainer.querySelectorAll(selector)
    for (const code of codes) {
      const rawCode = unescapeHTML(code.innerHTML)
      const functionType = (() => {
        if (/sequence/.test(code.className)) {
          return 'sequence'
        } else if (/plantuml/.test(code.className)) {
          return 'plantuml'
        } else if (/flowchart/.test(code.className)) {
          return 'flowchart'
        } else {
          return 'vega-lite'
        }
      })()
      const render = RENDER_MAP[functionType]
      const preParent = code.parentNode
      const diagramContainer = document.createElement('div')
      diagramContainer.classList.add(functionType)
      preParent.replaceWith(diagramContainer)
      const options = {}
      if (functionType === 'sequence') {
        Object.assign(options, { theme: this.muya.options.sequenceTheme })
      } else if (functionType === 'vega-lite') {
        Object.assign(options, {
          actions: false,
          tooltip: false,
          renderer: 'svg',
          theme: 'latimes' // only render light theme
        })
      }
      try {
        if (functionType === 'flowchart' || functionType === 'sequence') {
          const diagram = render.parse(rawCode)
          diagramContainer.innerHTML = ''
          diagram.drawSVG(diagramContainer, options)
        }
        if (functionType === 'plantuml') {
          const diagram = render.parse(rawCode)
          diagramContainer.innerHTML = ''
          diagram.insertImgElement(diagramContainer)
        }
        if (functionType === 'vega-lite') {
          await render(diagramContainer, JSON.parse(rawCode), options)
        }
      } catch (err) {
        diagramContainer.innerHTML = '< Invalid Diagram >'
      }
    }
  }

  mathRenderer = (math, displayMode) => {
    this.mathRendererCalled = true

    try {
      return katex.renderToString(math, {
        displayMode
      })
    } catch (err) {
      return displayMode
        ? `<pre class="multiple-math invalid">\n${math}</pre>\n`
        : `<span class="inline-math invalid" title="invalid math">${math}</span>`
    }
  }

  // render pure html by marked
  async renderHtml (toc) {
    this.mathRendererCalled = false
    let html = marked(this.markdown, {
      superSubScript: this.muya ? this.muya.options.superSubScript : false,
      footnote: this.muya ? this.muya.options.footnote : false,
      isGitlabCompatibilityEnabled: this.muya ? this.muya.options.isGitlabCompatibilityEnabled : false,
      highlight (code, lang) {
        // Language may be undefined (GH#591)
        if (!lang) {
          return code
        }

        if (DIAGRAM_TYPE.includes(lang)) {
          return code
        }

        const grammar = Prism.languages[lang]
        if (!grammar) {
          console.warn(`Unable to find grammar for "${lang}".`)
          return code
        }
        return Prism.highlight(code, grammar, lang)
      },
      emojiRenderer (emoji) {
        const validate = validEmoji(emoji)
        if (validate) {
          return validate.emoji
        } else {
          return `:${emoji}:`
        }
      },
      mathRenderer: this.mathRenderer,
      tocRenderer () {
        if (!toc) {
          return ''
        }
        return toc
      }
    })

    html = sanitize(html, EXPORT_DOMPURIFY_CONFIG, false)

    const exportContainer = this.exportContainer = document.createElement('div')
    exportContainer.classList.add('ag-render-container')
    exportContainer.innerHTML = html
    document.body.appendChild(exportContainer)

    // render only render the light theme of mermaid and diragram...
    await this.renderMermaid()
    await this.renderDiagram()
    let result = exportContainer.innerHTML
    exportContainer.remove()

    // hack to add arrow marker to output html
    const pathes = document.querySelectorAll('path[id^=raphael-marker-]')
    const def = '<defs style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">'
    result = result.replace(def, () => {
      let str = ''
      for (const path of pathes) {
        str += path.outerHTML
      }
      return `${def}${str}`
    })

    this.exportContainer = null
    return result
  }

  /**
   * Get HTML with style
   *
   * @param {*} options Document options
   */
  async generate (options) {
    const { printOptimization } = options

    // WORKAROUND: Hide Prism.js style when exporting or printing. Otherwise the background color is white in the dark theme.
    const highlightCssStyle = printOptimization ? `@media print { ${highlightCss} }` : highlightCss
    const html = this._prepareHtml(await this.renderHtml(options.toc), options)
    const katexCssStyle = this.mathRendererCalled ? katexCss : ''
    this.mathRendererCalled = false

    // `extraCss` may changed in the mean time.
    const { title, extraCss } = options
    return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>${sanitize(title, EXPORT_DOMPURIFY_CONFIG, true)}</title>
  <style>
  ${githubMarkdownCss}
  </style>
  <style>
  ${highlightCssStyle}
  </style>
  <style>
  ${katexCssStyle}
  </style>
  <style>
    .markdown-body {
      font-family: -apple-system,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
      box-sizing: border-box;
      min-width: 200px;
      max-width: 980px;
      margin: 0 auto;
      padding: 45px;
    }

    @media not print {
      .markdown-body {
        padding: 45px;
      }

      @media (max-width: 767px) {
        .markdown-body {
          padding: 15px;
        }
      }
    }

    .hf-container {
      color: #24292e;
      line-height: 1.3;
    }

    .markdown-body .highlight pre,
    .markdown-body pre {
      white-space: pre-wrap;
    }
    .markdown-body table {
      display: table;
    }
    .markdown-body img[data-align="center"] {
      display: block;
      margin: 0 auto;
    }
    .markdown-body img[data-align="right"] {
      display: block;
      margin: 0 0 0 auto;
    }
    .markdown-body li.task-list-item {
      list-style-type: none;
    }
    .markdown-body li > [type=checkbox] {
      margin: 0 0 0 -1.3em;
    }
    .markdown-body input[type="checkbox"] ~ p {
      margin-top: 0;
      display: inline-block;
    }
    .markdown-body ol ol,
    .markdown-body ul ol {
      list-style-type: decimal;
    }
    .markdown-body ol ol ol,
    .markdown-body ol ul ol,
    .markdown-body ul ol ol,
    .markdown-body ul ul ol {
      list-style-type: decimal;
    }
  </style>
  <style>${exportStyle}</style>
  <style>${extraCss}</style>
</head>
<body>
  ${html}
</body>
</html>`
  }

  /**
   * @private
   *
   * @param {string} html The converted HTML text.
   * @param {*} options The export options.
   */
  _prepareHtml (html, options) {
    const { header, footer } = options
    const appendHeaderFooter = !!header || !!footer
    if (!appendHeaderFooter) {
      return createMarkdownArticle(html)
    }

    if (!options.extraCss) {
      options.extraCss = footerHeaderCss
    } else {
      options.extraCss = footerHeaderCss + options.extraCss
    }

    let output = HF_TABLE_START
    if (header) {
      output += createTableHeader(options)
    }

    if (footer) {
      output += HF_TABLE_FOOTER
      output = createRealFooter(options) + output
    }

    output = output + createTableBody(html) + HF_TABLE_END
    return sanitize(output, EXPORT_DOMPURIFY_CONFIG, false)
  }
}

// Variables and function to generate the header and footer.
const HF_TABLE_START = '<table class="page-container">'
const createTableBody = html => {
  return `<tbody><tr><td>
  <div class="main-container">
    ${createMarkdownArticle(html)}
  </div>
</td></tr></tbody>`
}
const HF_TABLE_END = '</table>'

/// The header at is shown at the top.
const createTableHeader = options => {
  const { header, headerFooterStyled } = options
  const { type, left, center, right } = header
  let headerClass = type === 1 ? 'single' : ''
  headerClass += getHeaderFooterStyledClass(headerFooterStyled)
  return `<thead class="page-header ${headerClass}"><tr><th>
  <div class="hf-container">
    <div class="header-content-left">${left}</div>
    <div class="header-content">${center}</div>
    <div class="header-content-right">${right}</div>
  </div>
</th></tr></thead>`
}

/// Fake footer to reserve space.
const HF_TABLE_FOOTER = `<tfoot class="page-footer-fake"><tr><td>
  <div class="hf-container">
    &nbsp;
  </div>
</td></tr></tfoot>`

/// The real footer at is shown at the bottom.
const createRealFooter = options => {
  const { footer, headerFooterStyled } = options
  const { type, left, center, right } = footer
  let footerClass = type === 1 ? 'single' : ''
  footerClass += getHeaderFooterStyledClass(headerFooterStyled)
  return `<div class="page-footer ${footerClass}">
  <div class="hf-container">
    <div class="footer-content-left">${left}</div>
    <div class="footer-content">${center}</div>
    <div class="footer-content-right">${right}</div>
  </div>
</div>`
}

/// Generate the mardown article HTML.
const createMarkdownArticle = html => {
  return `<article class="markdown-body">${html}</article>`
}

/// Return the class whether a header/footer should be styled.
const getHeaderFooterStyledClass = value => {
  if (value === undefined) {
    // Prefer theme settings.
    return ''
  }
  return !value ? ' simple' : ' styled'
}

export default ExportHtml
